using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;
using System.Text.RegularExpressions;

using System.Linq;

namespace CallCreditStandards
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever there is a ........................................
    /// This rule only applies to .................................
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        CallCreditStandardsConstants.NameSpace,
        CallCreditStandardsConstants.DisallowAllCodeManipulationOfProjectDefinedObjectsRuleId,
        CallCreditStandardsConstants.ResourceBaseName,
        CallCreditStandardsConstants.DisallowAllCodeManipulationOfProjectDefinedObjects_RuleName,
        CallCreditStandardsConstants.CategoryDatabaseStructures,
        DescriptionResourceId = CallCreditStandardsConstants.DisallowAllCodeManipulationOfProjectDefinedObjects_ProblemDescription)]
    [SupportedElementType(typeof(ISqlProcedure))]
    [SupportedElementType(typeof(ISqlTrigger))]
    public class DisallowAllCodeManipulationOfProjectDefinedObjectsRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {
            // (Re)-Load Environment settings
            CallCreditSettings.LoadSettings();
            // Get Model collation 
            SqlComparer.Comparer = context.SchemaModel.Comparer;
        
            List<DataRuleProblem> problems = new List<DataRuleProblem>();

            IModelElement modelElement = context.ModelElement;

            // casting to SQL specific 
            SqlSchemaModel sqlSchemaModel = modelElement.Model as SqlSchemaModel;
            Debug.Assert(sqlSchemaModel != null, "SqlSchemaModel is expected");

            ISqlModelElement sqlElement = modelElement as ISqlModelElement;
            Debug.Assert(sqlElement != null, "ISqlModelElement is expected");

            // Get ScriptDom for this model element
            TSqlFragment sqlFragment = context.ScriptFragment as TSqlFragment;
            Debug.Assert(sqlFragment != null, "TSqlFragment is expected");

            // Refresh cached index/constraints/tables lists from Model
            CallCreditSettings.RefreshColumnCache(sqlSchemaModel);
            CallCreditSettings.RefreshConstraintsAndIndexesCache(sqlSchemaModel);

            // visitor to get the occurrences of statements 
            AlterIndexStatementVisitor alterIndexStatementVisitor = new AlterIndexStatementVisitor();
            sqlFragment.Accept(alterIndexStatementVisitor);
            List<AlterIndexStatement> alterIndexStatements = alterIndexStatementVisitor.Objects;

            AlterTableConstraintModificationStatementVisitor alterTableConstraintModificationStatementVisitor = new AlterTableConstraintModificationStatementVisitor();
            sqlFragment.Accept(alterTableConstraintModificationStatementVisitor);
            List<AlterTableConstraintModificationStatement> alterTableConstraintModificationStatements = alterTableConstraintModificationStatementVisitor.Objects;

            AlterTableDropTableElementStatementVisitor alterTableDropTableElementStatementVisitor = new AlterTableDropTableElementStatementVisitor();
            sqlFragment.Accept(alterTableDropTableElementStatementVisitor);
            List<AlterTableDropTableElementStatement> alterTableDropTableElementStatements = alterTableDropTableElementStatementVisitor.Objects;

            //DropClusteredConstraintFragmentOptionVisitor dropClusteredConstraintFragmentOptionVisitor = new DropClusteredConstraintFragmentOptionVisitor();
            //sqlFragment.Accept(dropClusteredConstraintFragmentOptionVisitor);
            //List<DropClusteredConstraintFragmentOption> dropClusteredConstraintFragmentOptions = dropClusteredConstraintFragmentOptionVisitor.Objects;

            //DropClusteredConstraintStateOptionVisitor dropClusteredConstraintStateOptionVisitor = new DropClusteredConstraintStateOptionVisitor();
            //sqlFragment.Accept(dropClusteredConstraintStateOptionVisitor);
            //List<DropClusteredConstraintStateOption> dropClusteredConstraintStateOptions = dropClusteredConstraintStateOptionVisitor.Objects;

            DropIndexStatementVisitor dropIndexStatementVisitor = new DropIndexStatementVisitor();
            sqlFragment.Accept(dropIndexStatementVisitor);
            List<DropIndexStatement> dropIndexStatements = dropIndexStatementVisitor.Objects;


            // some of this logic should be migrated into the visitors
            // particularlay the stuff re external names.
            // as this is how we do it elsewhere
            List<TSqlFragment> issues = new List<TSqlFragment>();
            foreach (var dis in dropIndexStatements)
            {
                foreach ( var dropIndexClause in dis.DropIndexClauses ) {
                    DropIndexClause dic = dropIndexClause as DropIndexClause;
                    BackwardsCompatibleDropIndexClause olddic = dropIndexClause as BackwardsCompatibleDropIndexClause;
                    String schemaName = null;
                    String tableName = null;
                    String indexName = null;
                    bool SkipExternalName = false;
                    if (dic != null)
                    {
                        if ( ( (dic.Object.DatabaseIdentifier != null
                                && String.IsNullOrEmpty(dic.Object.DatabaseIdentifier.Value)
                                )
                             || dic.Object.DatabaseIdentifier == null
                             )
                           && ( (dic.Object.ServerIdentifier != null
                                 && String.IsNullOrEmpty(dic.Object.ServerIdentifier.Value)
                                )
                              || dic.Object.ServerIdentifier == null
                             )
                           )
                        {
                            SkipExternalName = true;
                        }

                        schemaName = dic.Object.SchemaIdentifier.Value;
                        tableName = dic.Object.BaseIdentifier.Value;
                        indexName = dic.Index.Value;
                        
                    }
                    else if (olddic != null)
                    {
                        if (((olddic.Index.DatabaseIdentifier != null
                                && String.IsNullOrEmpty(olddic.Index.DatabaseIdentifier.Value)
                                )
                             || olddic.Index.DatabaseIdentifier == null
                             )
                           && ((olddic.Index.ServerIdentifier != null
                                 && String.IsNullOrEmpty(olddic.Index.ServerIdentifier.Value)
                                )
                              || olddic.Index.ServerIdentifier == null
                             )
                           )
                        {
                            SkipExternalName = true;
                        }

                        schemaName = olddic.Index.SchemaIdentifier.Value;
                        tableName = olddic.Index.BaseIdentifier.Value;
                        indexName = olddic.Index.ChildIdentifier.Value;
                    }
                    if ( ! SkipExternalName )
                    {
                        List<ISqlIndex> ixs = CallCreditSettings.getIndexes
                                .Where(n => SqlComparer.CompareEqual(n.Name.Parts[2], indexName)
                                         && SqlComparer.CompareEqual(n.Name.Parts[1], tableName)
                                         && SqlComparer.CompareEqual(n.Name.Parts[0], schemaName)
                                      )
                                .Select(n => n).ToList();
                        if (ixs.Count > 0)
                        {
                            issues.Add(dropIndexClause);
                        }
                    }
                }
            }
            foreach (var ais in alterIndexStatements)
            {
                if (   ais.Name.Value != null
                    // internal objects only
                    && ( (  ais.OnName.DatabaseIdentifier != null
                          && String.IsNullOrEmpty(ais.OnName.DatabaseIdentifier.Value)
                         )
                       || ais.OnName.DatabaseIdentifier == null
                       )
                    && ( ( ais.OnName.ServerIdentifier != null
                          && String.IsNullOrEmpty(ais.OnName.ServerIdentifier.Value)
                        )
                       || ais.OnName.ServerIdentifier == null
                       )
                    )
                {
                    List<ISqlIndex> ixs = CallCreditSettings.getIndexes
                            .Where(n => SqlComparer.CompareEqual(n.Name.Parts[2], ais.Name.Value)
                                && SqlComparer.CompareEqual(n.Name.Parts[1], ais.OnName.BaseIdentifier.Value)
                                && SqlComparer.CompareEqual(n.Name.Parts[0], ais.OnName.SchemaIdentifier.Value)
                                  )
                            .Select(n => n).ToList();
                    if (ixs.Count > 0)
                    {
                        issues.Add(ais);
                    }
                }
            }
            foreach (var atcms in alterTableConstraintModificationStatements)
            {
                // internal objects only
                if (((atcms.SchemaObjectName.DatabaseIdentifier != null
                        && String.IsNullOrEmpty(atcms.SchemaObjectName.DatabaseIdentifier.Value)
                        )
                     || atcms.SchemaObjectName.DatabaseIdentifier == null
                     )
                   && ((atcms.SchemaObjectName.ServerIdentifier != null
                         && String.IsNullOrEmpty(atcms.SchemaObjectName.ServerIdentifier.Value)
                        )
                      || atcms.SchemaObjectName.ServerIdentifier == null
                     )
                   )
                {
                    foreach (var consName in atcms.ConstraintNames)
                    {
                        List<ISqlPrimaryKeyConstraint> pkcs = CallCreditSettings.getPrimaryKeys.ToList()
                            .Where( n => n.Name != null
                                      && SqlComparer.CompareEqual(n.Name.Parts[0], atcms.SchemaObjectName.SchemaIdentifier.Value)
                                      && SqlComparer.CompareEqual(n.Name.Parts[1], consName.Value)
                                  )
                            .Select(n => n).ToList();

                        if (pkcs.Count > 0)
                        {
                            issues.Add(atcms);
                        }
                        else
                        {
                            List<ISqlForeignKeyConstraint> fkcs = CallCreditSettings.getForeignKeys.ToList()
                                .Where(n => n.Name != null
                                      && SqlComparer.CompareEqual(n.Name.Parts[0], atcms.SchemaObjectName.SchemaIdentifier.Value)
                                      && SqlComparer.CompareEqual(n.Name.Parts[1], consName.Value))
                                .Select(n => n).ToList();
                            if (fkcs.Count > 0)
                            {
                                issues.Add(atcms);
                            }
                            else
                            {
                                List<ISqlUniqueConstraint> ukcs = CallCreditSettings.getUniqueConstraints.ToList()
                                    .Where(n => n.Name != null
                                        && SqlComparer.CompareEqual(n.Name.Parts[0], atcms.SchemaObjectName.SchemaIdentifier.Value)
                                        && SqlComparer.CompareEqual(n.Name.Parts[1], consName.Value))
                                    .Select(n => n).ToList();
                                if (ukcs.Count > 0)
                                {
                                    issues.Add(atcms);
                                }
                            }
                        }
                    }
                }
            }

            foreach (var atdtes in alterTableDropTableElementStatements)
            {
                // internal objects only
                if (((atdtes.SchemaObjectName.DatabaseIdentifier != null
                        && String.IsNullOrEmpty(atdtes.SchemaObjectName.DatabaseIdentifier.Value)
                        )
                     || atdtes.SchemaObjectName.DatabaseIdentifier == null
                     )
                   && ((atdtes.SchemaObjectName.ServerIdentifier != null
                         && String.IsNullOrEmpty(atdtes.SchemaObjectName.ServerIdentifier.Value)
                        )
                      || atdtes.SchemaObjectName.ServerIdentifier == null
                     )
                   )
                {
                    foreach (var dropElement in atdtes.AlterTableDropTableElements
                                    .Where( n => n.TableElementType == TableElementType.Constraint).Select(n=>n) 
                            )
                    {
                        List<ISqlPrimaryKeyConstraint> pkcs = CallCreditSettings.getPrimaryKeys.ToList()
                            .Where(n => n.Name != null
                                      && SqlComparer.CompareEqual(n.Name.Parts[0], atdtes.SchemaObjectName.SchemaIdentifier.Value)
                                      && SqlComparer.CompareEqual(n.Name.Parts[1], dropElement.Name.Value)
                                  )
                            .Select(n => n).ToList();

                        if (pkcs.Count > 0)
                        {
                            issues.Add(atdtes);
                        }
                        else
                        {
                            List<ISqlForeignKeyConstraint> fkcs = CallCreditSettings.getForeignKeys.ToList()
                                .Where(n => n.Name != null
                                      && SqlComparer.CompareEqual(n.Name.Parts[0], atdtes.SchemaObjectName.SchemaIdentifier.Value)
                                      && SqlComparer.CompareEqual(n.Name.Parts[1], dropElement.Name.Value))
                                .Select(n => n).ToList();
                            if (fkcs.Count > 0)
                            {
                                issues.Add(atdtes);
                            }
                            else
                            {
                                List<ISqlUniqueConstraint> ukcs = CallCreditSettings.getUniqueConstraints.ToList()
                                    .Where(n => n.Name != null
                                        && SqlComparer.CompareEqual(n.Name.Parts[0], atdtes.SchemaObjectName.SchemaIdentifier.Value)
                                        && SqlComparer.CompareEqual(n.Name.Parts[1], dropElement.Name.Value))
                                    .Select(n => n).ToList();
                                if (ukcs.Count > 0)
                                {
                                    issues.Add(atdtes);
                                }
                            }
                        }
                    }
                }
            }

            // Create problems for each object
            foreach (TSqlFragment issue in issues)
            {
                DataRuleProblem problem = new DataRuleProblem(this,
                                            String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                            sqlElement);

                SqlRuleUtils.UpdateProblemPosition(problem, issue.StartOffset, issue.FragmentLength);
                problems.Add(problem);
            }


            return problems;
        }

        #endregion    
 
    }
}
